package com.wh1200.mybatis.xmlless.resolver

import com.wh1200.mybatis.xmlless.AnnotatedElementDetailInfo
import com.wh1200.mybatis.xmlless.annotations.JsonMappingProperty
import com.wh1200.mybatis.xmlless.constant.ClassNames
import com.wh1200.mybatis.xmlless.model.MatchedParameter
import com.wh1200.mybatis.xmlless.model.MethodInfo
import com.wh1200.mybatis.xmlless.model.criteria.*
import com.wh1200.mybatis.xmlless.util.ClassUtils
import org.apache.ibatis.reflection.ParamNameResolver
import org.apache.ibatis.session.Configuration
import java.lang.reflect.Method

/**
 *
 * Created by 吴昊 on 2018/12/19.
 *
 * @author 吴昊
 * @since 1.4.3
 */
object ParameterResolver {

  private val PARAMETER_NAMES_CACHE = HashMap<Method, Array<String>>()

  fun createOptionalParameters(
      parameters: List<ParameterInfo>
  ): List<OptionalParam> {
    val list = mutableListOf<OptionalParam>()
    parameters.forEach {
      if (it.isCollection) {
        it.realType
      }

      if (it.isComplex) {
        list.add(
            OptionalParam(
                null, it.name,
                it.parameter
            )
        )
        list.addAll(
            resolveParametersInComplexObject(
                parameters.size > 1 || parameters.first().specificParamName != null,
                it
            )
        )
      } else {
        list.add(
            OptionalParam(
                null, it.name,
                it.parameter
            )
        )
      }
    }
    return list
  }

  fun buildCriteriaParameter(
      name: CriteriaParamName,
      element: AnnotatedElementDetailInfo?,
      specificValue: Boolean = false
  ): CriteriaParameter {
    return CriteriaParameter(
        name, element, specificValue,
        /** whether parameter is marked with annotation @NotNull */
        element != null && element.isNotNull,
        /** whether parameter is marked with annotation @NotBlank */
        element != null && element.isNotBlank,
        /** whether parameter is marked with annotation @NotEmtpy */
        element != null && element.isNotEmpty,
        element?.testTypes ?: listOf(),
        element?.isNoIfWrap ?: false

    )
  }

  fun isNotComplexType(className: String, allInterfaces: List<String>): Boolean {
    return ClassNames.SPRING_PAGEABLE in allInterfaces
        || ClassNames.MYBATIS_PLUS_PAGE in allInterfaces
        || ClassNames.SPRING_DATA_PAGE in allInterfaces
        || className.startsWith("java.")
        || className == ClassNames.SPRING_DATA_SORT
        || className == ClassNames.SPRING_DATA_SORT_DIRECTION
  }

  fun isComplexType(type: Class<*>): Boolean {
    val realType = TypeResolver.resolveRealType(type)
    val allInterfaces = ClassUtils.getAllClassOrInterface(realType).map { it.name }
    return realType != Void.TYPE
        && !realType.isPrimitive
        && !realType.isArray
        && !realType.isEnum
        && !isNotComplexType(realType.name, allInterfaces)
  }

  /**
   * 解析参数名称对象的参数或者对象属性
   */
  fun resolve(paramName: String, methodInfo: MethodInfo): MatchedParameter? {
    val paramNames = methodInfo.paramNames
    val paramIndex = paramNames.indexOf(paramName)
    // 当参数名称在方法的直接参数列表中
    if (paramIndex >= 0) {
      return MatchedParameter(methodInfo.parameters[paramIndex], paramNames[paramIndex])
    } else {
      // 当参数名称不再方法的直接参数列表中时， 参数可能是被包装成对象，因此在复杂对象中查找
      // 复杂对象是排除了 基本类型、java包下的类型、数组类型、枚举类型、及Pageable、Sort和Order等spring data类型的剩余参数
      val complexParameterMap = HashMap<Int, ParameterInfo>()
      methodInfo.parameters.forEachIndexed { index, parameter ->
        if (parameter.isComplex) {
          complexParameterMap[index] = parameter
        }
      }
      complexParameterMap.forEach { complexParameterPair ->
        val realType = complexParameterPair.value.realType ?: complexParameterPair.value.type
        val matchedProperty = realType.fields.firstOrNull { property ->
          property.name == paramName
              || "${complexParameterPair.value.name}.${property.name}" == paramName
        }
        if (matchedProperty != null) {
          if (methodInfo.hasOnlyComplexParameter
              && methodInfo.parameters.first().specificParamName == null
          ) {
            return MatchedParameter(complexParameterPair.value, matchedProperty.name)
          }
          return MatchedParameter(
              complexParameterPair.value,
              paramNames[complexParameterPair.key] + "." + matchedProperty.name
          )
        }
      }
    }
    methodInfo.modelFields.forEach { field ->
      if (field.name == paramName) {
        return MatchedParameter(
            buildParameterInfo(
                field
            ),
            paramName
        )
      }
    }
    return null
  }

  fun buildParameterInfo(detail: AnnotatedElementDetailInfo): ParameterInfo {
    return ParameterInfo(
        detail, detail.name,
        detail.typeInfo,
        detail.chooseCriteria
    )
  }

  fun resolveNames(method: Method): Array<String> {
    return if (PARAMETER_NAMES_CACHE[method] != null) {
      PARAMETER_NAMES_CACHE[method]!!
    } else {
      val names = ParamNameResolver(
          Configuration().apply {
            this.isUseActualParamName = true
          }, method
      ).names
      PARAMETER_NAMES_CACHE[method] = names
      names
    }
  }

  private fun resolveParametersInComplexObject(
      forcePrefix: Boolean,
      parameterInfo: ParameterInfo
  ): List<OptionalParam> {
    val list = mutableListOf<OptionalParam>()
    val prefix = parameterInfo.parameter.paramName
    parameterInfo.type.fields.forEach { field ->
      val paramName = field.specificName
      val name = field.paramName
      if (forcePrefix) {
        list.add(OptionalParam(prefix, name, field))
      } else {
        list.add(OptionalParam(parameterInfo.specificParamName ?: paramName, name, field))
      }
    }
    return list.toList()
  }

}
